home *** CD-ROM | disk | FTP | other *** search
/ CU Amiga Super CD-ROM 19 / CU Amiga Magazine's Super CD-ROM 19 (1998)(EMAP Images)(GB)[!][issue 1998-02].iso / CUCD / Utilities / Scion / ARexx / GEDCOM2Scion.rexx < prev    next >
OS/2 REXX Batch file  |  1997-11-04  |  49KB  |  1,485 lines

  1. /****************************************************************************
  2.  *                                                                          *
  3.  *                                                                          *
  4.  * $VER: GEDCOM2Scion.rexx 2.32 (30 Oct 1997)
  5.  *                                                                          *
  6.  *                      Written by Freddy Ariës                             *
  7.  * Address: Lindeboomweg 7, NL-7135 KE Harreveld, The Netherlands.          *
  8.  *                                                                          *
  9.  * This program was created to import GEDCOM data into the Scion database.  *
  10.  * It should work pretty good by now, although no guarantees whatsoever     *
  11.  * are made. If you have problems using this script, please contact me, and *
  12.  * describe exactly what the problem is, or better yet, send me a copy of   *
  13.  * the GEDCOM file you are trying to read), and I will try to fix it.       *
  14.  *                                                                          *
  15.  * GEDCOM was developed by the Family History Department of the Church of   *
  16.  * Jesus Christ of Latter-day Saints to provide a flexible uniform format   *
  17.  * for exchanging computerized genealogical data.  GEDCOM is an acronym for *
  18.  * GEnealogical Data COMmunication.  GEDCOM is provided to foster the       *
  19.  * sharing of genealogical information and the development of a wide range  *
  20.  * of inter-operable software products to assist genealogists, historians,  *
  21.  * and other researchers.                                                   *
  22.  *                                                                          *
  23.  * + SCION must be running for this AREXX script to work.                   *
  24.  * + This script uses (by default) the rexxreqtools.library (which requires *
  25.  *   a version of reqtools larger than 2.0 and rexxsyslib.library).         *
  26.  *   If you do not have these, run SetDefaults.rexx to change the settings. *
  27.  * + Even though this script does no parsing of dates, it's safer if they   *
  28.  *   are in the exact format "DD MMM YYYY".                                 *
  29.  *                                                                          *
  30.  * DONE: - progress indicator, using rexxarplib.library (requested by       *
  31.  *         Robbie J. Akins himself).                                        *
  32.  *       - now also recognizes full formal tag-names... due to the way eg.  *
  33.  *         Family Tree Maker for Windows creates its (PAF) GEDCOM output.   *
  34.  *       - should now correctly parse files containing ^M (carriage return) *
  35.  *         characters (usually MS-DOS ASCII files that weren't stripped).   *
  36.  *       - creation of external note files for multi-line GEDCOM comments   *
  37.  *         (option)                                                         *
  38.  *       - use of SOUR structure data for the Reference fields (currently   *
  39.  *         not very smart)                                                  *
  40.  *       - now uses preference file for default settings                    *
  41.  *       - improved skipping of (unrecognized) structures                   *
  42.  *       - recognition and parsing of GEDCOM dates for Scion v5             *
  43.  *                                                                          *
  44.  * All unrecognized fields or fields that Scion doesn't use, are skipped.   *
  45.  * NOTES:                                                                   *
  46.  * + The program generates a file FILENAME.log (where FILENAME is the       *
  47.  *  name of the GEDCOM file read), in the directory where the GEDCOM file   *
  48.  *  is located. This .log file contains parsing info about which lines were *
  49.  *  skipped and which non-fatal errors were encountered. It may be a good   *
  50.  *  idea to read this file!                                                 *
  51.  * + FAMS and FAMC fields, and EVEN structures will always be skipped,      *
  52.  *  because I use another method of establishing family (spouse & children) *
  53.  *  relationships. If no relationships are established, this probably means *
  54.  *  that the imported file does not support that other method. If you       *
  55.  *  encounter such a file, please send it to me, and tell me what program   *
  56.  *  generated it. If this happens a lot, I will add support for the parsing *
  57.  *  of these relations in a future version.                                 *
  58.  * + If you see strange strings in the Reference fields (eg. something like *
  59.  *  "R1"), you may be able to find more reference information in the GEDCOM *
  60.  *  file in the SOUR structure with that reference number (eg. @R1@).       *
  61.  *                                                                          *
  62.  * TO DO (but low priority, unless someone really wants this [?]):          *
  63.  *  - Import GEDCOM address data into Scion V5 personal/family addresses    *
  64.  *    (soon!)                                                               *
  65.  *  - Add Shell options for the processing of note files                    *
  66.  *  - More intelligent processing of SOUR structures for Reference fields   *
  67.  *  - Add support for other character sets (like the ANSEL format that is   *
  68.  *    described in the GEDCOM specification) [external conversion program?] *
  69.  *  - Add support for EVEN(t) structures                                    *
  70.  *  - Maybe someday even a way to allow modifying an existing database.     *
  71.  *    The current version will only add to a database, and doesn't care for *
  72.  *    double entries. Don't hold your breath for this one, though!          *
  73.  *  - Suggestions, comments, bugreports, donations, etc. are appreciated.   *
  74.  *                                                                          *
  75.  ****************************************************************************/
  76.  
  77. options failat 20; options results
  78. arg inname inval
  79.  
  80. versionstr = "2.32"
  81.  
  82. /* Don't change the settings here! Run SetDefaults.rexx instead! */
  83. usereq = 1; prgrs = 1; pgopen = 0; outp = 1
  84. scrdev = stdout
  85. PSCR = "SCIONGEN"
  86. notesdir = ""
  87.  
  88. scrname = "CON:0//639//Scion_Output/AUTO/WAIT/CLOSE/SCREEN"
  89. donotes = 0; lnum = 0
  90. NL = '0A'x
  91.  
  92. signal on IOERR
  93.  
  94. do while inname = '?'
  95.   writeln(stdout, "INFILE/A,QUIET/S,NOREQ/S ")
  96.   pull inname inval
  97. end
  98.  
  99. /* read preferences file */
  100.  
  101. if open(pfile, 'ENV:Scion/ScionRexx.prefs', 'r') then do
  102.   do while ~eof(pfile)
  103.     inln = readln(pfile)
  104.     if inln ~= "" then do
  105.       wstr = upper(word(inln, 1))
  106.       if wstr = "NOTES" then
  107.         notesdir = strip(delstr(inln, 1, length(wstr)), 'b', ' "')
  108.       else if wstr = "USEREQ" then
  109.         usereq = 1
  110.       else if wstr = "NOUSEREQ" then
  111.         usereq = 0
  112.       else if wstr = "PROGRESS" then
  113.         prgrs = 1
  114.       else if wstr = "NOPROGRESS" then
  115.         prgrs = 0
  116.       else if wstr = "PUBSCREEN" then
  117.         pscr = strip(delstr(inln, 1, length(wstr)), 'b', ' "')
  118.     end
  119.   end
  120.   close(pfile)
  121. end
  122.  
  123. if pscr = "" | (pscr ~= "WORKBENCH" & ~show('p', pscr)) then
  124.   pscr = "SCIONGEN"
  125. wstr = right(notesdir, 1)
  126. if wstr ~= '/' & wstr ~= ':' then notesdir = ""
  127. scrname = scrname||pscr
  128.  
  129. /* parse command line options, to enable calling the script automatically,
  130.  * eg. from a function key. This gets priority over global settings!
  131.  */
  132.  
  133. if inname ~= "" then do
  134.   if inname = "QUIET" | inname = "NOREQ" then do
  135.     inval = inname; inname = ""
  136.   end
  137. end
  138.  
  139. if inval = "QUIET" then do
  140.   outp = 0; usereq = 0
  141. end
  142. else if inval = "NOREQ" then usereq = 0
  143.  
  144. if usereq & ~show('l','rexxreqtools.library') then do
  145.   if exists('libs:rexxreqtools.library') then
  146.     call addlib('rexxreqtools.library',0,-30,0)
  147.   else do
  148.     usereq = 0; outp = 1
  149.     Tell("Unable to open rexxreqtools.library - using text output")
  150.   end
  151. end
  152.  
  153. if ~usereq then prgrs = 0
  154.  
  155. if ~show('l','rexxarplib.library') then do
  156.   if exists('libs:rexxarplib.library') then do
  157.     /* rexxarplib is present - start it */
  158.     call addlib('rexxarplib.library',0,-30,0)
  159.     screentofront(pscr)
  160.   end
  161.   else
  162.     prgrs = 0
  163. end
  164. else do
  165.   /* rexxarplib is already in memory */
  166.   screentofront(pscr)
  167. end
  168.  
  169. if ~show('P','SCIONGEN') then do
  170.   EndString('I am sorry to say that the SCION Genealogist' || NL ||,
  171.     'database is not available. Please start the' || NL ||,
  172.     'SCION program BEFORE using this script!')
  173. end
  174.  
  175. myport = "SCIONGEN"
  176. address value myport
  177. GETDBNAME
  178. dbname = upper(RESULT)
  179. GETPROGVERSION
  180. prgvers = RESULT
  181.  
  182. if outp & ~usereq then do
  183.   if pscr ~= "WORKBENCH" then do
  184.     scrdev = 'SCNG2SSCR'
  185.     if ~open(scrdev, scrname, 'w') then scrdev = stdout
  186.   end
  187.   Tell("GEDCOM to Scion conversion script v"||versionstr||" by Freddy Ariës")
  188.   Tell("Scion (output) database: "||dbname)
  189. end
  190.  
  191. if inname = "" then do
  192.   /* ignore the value of outp; if we can't ask for the input file,
  193.    * we can't do anything!
  194.    */
  195.   if usereq then do
  196.     /* We need a file requester for further data */
  197.     inname = rtfilerequest(,,'GEDCOM Input File',,'rt_pubscrname = '||PSCR||'   rtfi_initialpath = RAM:',)
  198.   end
  199.   else do
  200.     Tell("Please enter the filename (with complete path) of the GEDCOM file:")
  201.     TellNN("Input file: ")
  202.     inname = readln(scrdev)
  203.     inname = strip(inname, 'b', ' "')
  204.   end
  205.   if inname = '' then
  206.     EndString("ERROR: No Input File!")
  207. end
  208.  
  209. if ~open(infile, inname, "r") then
  210.   EndString("ERROR: Input file '"inname"' not found!")
  211.  
  212. if outp then do
  213.   if usereq then do
  214.     donotes = rtezrequest("Create external Note files for Scion for "||,
  215.               NL||'long GEDCOM comment lines?'||,
  216.               '',' _Yes| _No ','Input Request:','rt_pubscrname = '||pscr)
  217.     if donotes then do      
  218.       prstot='Scion Database name: '||dbname||NL
  219.       if notesdir~="" then do
  220.         prstot=prstot||'Scion Notes directory: '||NL||'"'||notesdir||'"'||NL
  221.         prstot=prstot||'The Note files will be created using this name,'||NL
  222.         prstot=prstot||'and in this directory.'||NL
  223.       end
  224.       else
  225.         prstot=prstot||'The Note files will be created using this name.'||NL
  226.       prstot=prstot||'If that is not what you want, abort and save'||NL
  227.       prstot=prstot||'your (possibly empty) database first under a'||NL
  228.       prstot=prstot||'different name! '||NL||'    Please make your choice: '
  229.       docont = rtezrequest(prstot,' _Continue | _Abort ','Converter Message:','rt_pubscrname = '||PSCR)
  230.       if docont = 0 then EXIT; /* EndString("Aborted!") */
  231.       if notesdir = "" then do
  232.         notesdir = rtfilerequest(,,'Select Notes Directory:','_Ok','rt_pubscrname = '||pscr||'   rtfi_flags = freqf_nofiles   rtfi_initialpath = RAM:',fres)
  233.         if fres = 0 then donotes = 0
  234.           /* User cancelled requester: no external note files will be created */
  235.         /* For future use:
  236.         if donotes then do
  237.           ntovw = rtezrequest('Overwrite existing note files?'||,
  238.             '',' _Yes | _No ','Converter Message:','rt_pubscrname = '||PSCR)
  239.         end
  240.      */
  241.       end
  242.     end
  243.   end
  244.   else do
  245.     Tell("Create external Note files for Scion for long")
  246.     TellNN("GEDCOM comment lines (y/n)? ")
  247.     innote = readln(scrdev)
  248.     innote = upper(left(innote, 1))
  249.     if innote = "Y" then donotes = 1
  250.     else donotes = 0
  251.     Tell("")
  252.     if donotes then do
  253.       innote = ""
  254.       do until innote = "Y" | innote = "N"
  255.         Tell("Scion Database name: "||dbname)
  256.         if notesdir ~= "" then do
  257.           Tell("Scion Notes directory: "||NL||'"'||notesdir||'"')
  258.           Tell("The Note files will be created using this name,")
  259.           Tell("and in this directory.")
  260.         end
  261.         else
  262.           Tell("The Note files will be created using this name.")
  263.         Tell("If that is not what you want, abort and save your")
  264.         Tell("(possibly empty) database first under a different name!")
  265.         TellNN("Continue (y/n): ")
  266.         innote = readln(scrdev)
  267.         innote = upper(left(innote, 1))
  268.       end
  269.       if innote ~= "Y" then EndString("Aborted.")
  270.       if notesdir = "" then do
  271.         ptmp = ""
  272.         do until ptmp = ":" | ptmp = "/"
  273.           Tell("Enter full directory name where Scion's note files are located")
  274.           TellNN("(MUST end with ':' or '/'): ")
  275.           innote = readln(scrdev)
  276.           innote = strip(innote, 'b', ' "')
  277.           ptmp = right(innote, 1)
  278.         end
  279.         notesdir = innote
  280.       end
  281.       /* For future use:
  282.       TellNN("Overwrite existing Note files? (y/n): ")
  283.       innote = readln(scrdev)
  284.       innote = upper(left(innote, 1))
  285.       if innote = "Y" then ntovw = 1
  286.       else ntovw = 0
  287.       */
  288.     end
  289.   end
  290. end
  291.  
  292. ntovw = 1
  293.  
  294. if ~usereq then
  295.   Tell("Be patient - this may take a while...")
  296.  
  297. /* Initialize line count, individual counter and family counter */
  298. ink = GetNextLine()
  299. if left(ink, 6) ~= "0 HEAD" then do
  300.   close(infile)
  301.   EndString("ERROR: Invalid beginning of file - not a valid GEDCOM format")
  302. end
  303.  
  304. lvlstr = '0'; lvl = 1; atlvl = 1
  305. IRNArr.0 = ''; IRNArr.1 = ''; FGRNArr.0 = ''; FGRNArr.1 = ''
  306.  
  307. /* Read the "HEAD(ER)" section until we find something else of level "0" */
  308.  
  309. prstot = ""
  310. ink = ParseHeader(atlvl)
  311. prsr = "Destination: Scion Genealogist "||prgvers
  312. if ~usereq then
  313.   Tell(prsr)
  314. else
  315.   prstot = prstot||prsr||NL
  316. prsr = "Dest. file:  "||dbname
  317. if ~usereq then
  318.   Tell(prsr||NL||"Scanning file for persons...")
  319. else do
  320.   prstot=prstot||prsr||NL||NL||"Parsing will take a while - be patient."||,
  321.     NL||"Click `Continue' to start parsing..."
  322.   rv = rtezrequest(prstot,'_Continue| _Abort ','Converter Message:','rt_pubscrname = '||PSCR)
  323.   if rv = 0 then EXIT
  324. end
  325.  
  326. /* TO DO: if inname ends on .GED, strip the extension */
  327. if ~open(errfile, inname||".log", "w") then
  328.   errfile = stdout
  329.  
  330. /* Now scan the following level "0" fields for individuals;
  331.  * skip the families, for the moment
  332.  */
  333.  
  334. irn = 0; famline = 0
  335.  
  336. if prgrs then do
  337.   Postmsg(10, 10, "GEDCOM to Scion (by Freddy Ariës)\Database: "||,
  338.     StripPath(inname)||"\Persons parsed: "||irn||"\ ", PSCR)
  339.   pgopen = 1
  340. end
  341.  
  342. replay = 0
  343. do while ~eof(infile)
  344.   lvlstr = word(ink, 1)
  345.   lvl = GetNumType(lvlstr)
  346.  
  347.   if lvl = atlvl then do
  348.     tagstr = upper(word(ink, words(ink)))
  349.     if (tagstr="INDI" | tagstr="INDIVIDUAL") then do
  350.       nstr = compress(word(ink, 2), '@ ')
  351.       tp = GGetIRN(nstr)
  352.       if tp ~= 0 then
  353.         writeln(errfile, "ERROR: Duplicate person encountered: "||nstr||" (IRN "||tp||") (line: "||lnum||")")
  354.       irn = irn + 1
  355.       if pgopen then Postmsg(,, "\\Persons parsed: "||irn||"\ ", PSCR)
  356.       ink = ParsePerson(nstr, lvl)
  357.       if ink ~= "" then replay = 1
  358.     end
  359.     else if ((tagstr="FAM" | tagstr="FAMILY") & famline = 0) then
  360.       famline=lnum
  361.   end
  362.   /* Skip all lines with level ~= current level (0) */
  363.   if replay = 0 then ink = GetNextLine()
  364.   else replay = 0
  365. end
  366.  
  367. if ~usereq then
  368.   Tell("Number of persons parsed: "||irn)
  369.  
  370. /* Now rescan the entire file for FAMilies; I know it is quite
  371.  * inefficient this way, but it's better to add all the persons first,
  372.  * and then establish the relations...
  373.  */
  374.  
  375. replay = 0
  376. fgrn = 0; fxs = 0
  377.  
  378. if ~usereq then
  379.   Tell("Scanning file again to establish relations...")
  380.  
  381. if pgopen then Postmsg(,, "\\\Families parsed: 0 (scanning...)", PSCR)
  382.  
  383. /* If we've already passed the first FAM line, go back to that line
  384.  * in the file. Otherwise, just continue where we are.
  385.  */
  386. if famline > 0 then do
  387.   famline = famline - 1
  388.   close(infile)
  389.   if ~open(infile, inname, 'r') then
  390.     EndString("ERROR: Unable to read relations!")
  391.   lvlstr = '0'; lvl = 1; atlvl = 1; lnum = 0
  392.   do while ~eof(infile) & lnum < famline
  393.     lnum = lnum + 1
  394.     ink = readln(infile)
  395.   end
  396. end
  397.  
  398. do while ~eof(infile)
  399.   if replay = 0 then ink = GetNextLine()
  400.   else replay = 0
  401.  
  402.   lvlstr = word(ink, 1)
  403.   lvl = GetNumType(lvlstr)
  404.  
  405.   if lvl = atlvl then do
  406.     tagstr = upper(word(ink, words(ink)))
  407.     if (tagstr = "FAM" | tagstr = "FAMILY") then do
  408.       nstr = compress(word(ink, 2),'@ ')
  409.       fp = GGetFGRN(nstr)
  410.       if fp ~= 0 then
  411.         writeln(errfile, "WARNING: Duplicate family encountered: "||nstr||" (FGRN "||fp||") (line: "||lnum||")")
  412.         /* TO DO: is the error message necessary? Or can we simply go on? */
  413.       else
  414.         fgrn = fgrn + 1
  415.       if pgopen then Postmsg(,, "\\\Families parsed: "||fgrn, PSCR)
  416.       ink = ParseFamily(nstr, lvl)
  417.       if ink ~= "" then replay = 1
  418.     end
  419.     else if (tagstr = "TRLR" | tagstr = "TRAILER") then do
  420.       close(infile)
  421.       if pgopen then do
  422.         Postmsg()
  423.         pgopen = 0
  424.       end
  425.       if usereq then do
  426.         EndString("PARSING DONE:"||NL||"Number of persons parsed: "||irn||,
  427.           NL||"Number of families parsed: "||fgrn||,
  428.       NL||NL||"DON'T FORGET TO SAVE YOUR SCION FILE!!!")
  429.       end
  430.       else do
  431.     EndString("Number of families parsed: "||fgrn||NL||,
  432.            NL||"DONE! DON'T FORGET TO SAVE YOUR SCION FILE!!!")
  433.       end
  434.     end
  435.   end
  436.   /* Skip all the fields at lvl ~= this level */
  437. end
  438. close(infile)
  439. if (ink ~= "0 TRLR") & (ink ~= "0 TRAILER") then
  440.   EndString("ERROR: Unexpected end of file")
  441. else
  442.   EndString("ERROR: Trailer not recognized! (line: "||lnum||")")
  443.  
  444. ParseHeader: PROCEDURE EXPOSE infile prstot NL outp usereq scrdev lnum pgopen pscr
  445. parse arg inilvl
  446. do while ~eof(infile)
  447.   ins = GetNextLine()
  448.   if ins = "" then
  449.     EndString("ERROR: Unexpected end of file")
  450.   lvlstr = word(ins, 1)
  451.   lvl = GetNumType(lvlstr)
  452.   if lvl <= inilvl then RETURN ins
  453.   if lvl = inilvl+1 then do
  454.     lstr = strip(delstr(ins, 1, length(lvlstr)))
  455.     if left(curr,4) = "SOUR" then do
  456.       lstr = strip(delstr(lstr, 1, length(curr)))
  457.       prsr = "Source system: "||lstr
  458.       if ~usereq then
  459.     Tell(prsr)
  460.       else
  461.         prstot = prstot||prsr||NL
  462.       ins = ParseSource(lvl)
  463.       lvlstr = word(ins, 1)
  464.       lvl = lvlstr + 1
  465.       if lvl <= inilvl then RETURN ins
  466.       if lvl = inilvl+1 then do
  467.         lstr = strip(delstr(ins, 1, length(lvlstr)))
  468.         curr = upper(word(lstr, 1))
  469.       end
  470.       else EndString("ERROR: This should never happen [1] (line: "||lnum||")")
  471.     end
  472.     if curr = "DATE" then do
  473.       lstr = strip(delstr(lstr, 1, length(curr)))
  474.       prsr = "Creation date: "||lstr
  475.       if ~usereq then
  476.     Tell(prsr)
  477.       else
  478.         prstot = prstot||prsr||NL
  479.     end
  480.     else if curr = "FILE" then do
  481.       lstr = strip(delstr(lstr, 1, length(curr)))
  482.       prsr = "Source file:   "||lstr
  483.       if ~usereq then
  484.     Tell(prsr)
  485.       else
  486.         prstot = prstot||prsr||NL
  487.     end
  488.     /* add COPR (copyright) and GEDC VERS parsing
  489.      */
  490.   end
  491. end
  492. EndString("ERROR: Unexpected end of file")
  493.  
  494. ParseSource: PROCEDURE EXPOSE infile prstot NL outp usereq scrdev lnum pgopen pscr
  495. parse arg namlvl
  496. /* Scan for "NAME" and "VERS" */
  497. do while ~eof(infile)
  498.   ins = GetNextLine()
  499.   if ins = "" then
  500.     EndString("ERROR: Unexpected end of file")
  501.   lvlstr = word(ins, 1)
  502.   lvl = GetNumType(lvlstr)
  503.   if lvl <= namlvl then RETURN ins
  504.   if lvl = namlvl+1 then do
  505.     lstr = strip(delstr(ins, 1, length(lvlstr)))
  506.     curr = left(upper(word(lstr, 1)),4)
  507.     if curr = "VERS" then do
  508.       lstr = strip(delstr(lstr, 1, length(curr)))
  509.       prsr = "Version:       "||lstr
  510.       if ~usereq then
  511.         Tell(prsr)
  512.       else
  513.         prstot = prstot||prsr||NL
  514.     end
  515.     else if curr = "NAME" then do
  516.       lstr = strip(delstr(lstr, 1, length(curr)))
  517.       prsr = "Created by:    "||lstr
  518.       if ~usereq then
  519.         Tell(prsr)
  520.       else
  521.         prstot = prstot||prsr||NL
  522.     end
  523.   end
  524. end
  525. EndString("ERROR: Unexpected end of file")
  526.  
  527. ParsePerson: PROCEDURE EXPOSE infile IrnArr. errfile outp usereq scrdev lnum donotes dbname ntovw notesdir pgopen pscr prgvers
  528. parse arg pnum, inilvl
  529. replay = 0
  530. prn = GetNewPerson()
  531. IRNArr.0 = IRNArr.0||pnum||' '
  532. IRNArr.1 = IRNArr.1||prn||' '
  533. noteset = 0; refset = 0; oldnotestr = ""
  534. do while ~eof(infile)
  535.   if replay = 0 then ins = GetNextLine()
  536.   else replay = 0
  537.   if ins = "" then
  538.     EndString("ERROR: Unexpected end of file")
  539.  
  540.   lvlstr = word(ins, 1)
  541.   lvl = GetNumType(lvlstr)
  542.   if lvl <= inilvl then RETURN ins
  543.   if lvl = inilvl + 1 then do
  544.     lstr = strip(delstr(ins, 1, length(lvlstr)))
  545.     curr = upper(word(lstr, 1))
  546.     restcurr = delstr(lstr, 1, length(curr))
  547.     if curr="FAMILY_CHILD" then curr = "FAMC"
  548.     else if curr="FAMILY_SPOUSE" then curr = "FAMS"
  549.     else if curr = "REFERENCE" then curr = "REFN"
  550.     else if curr = "CHRISTENING" | curr = "ADULT_CHRISTENING" then curr = "CHR"
  551.     else if length(curr) > 4 then curr = left(curr, 4)
  552.   end
  553.  
  554.   if curr = "NAME" then StorePersName(strip(restcurr), prn)
  555.   else if curr = "SEX" then StorePersSex(strip(restcurr), prn)
  556.   else if (curr="BIRT" | curr="DEAT" | curr="BURI" | curr="CHR" | curr="BAPM" | curr="BAPL" | curr="CHRA" | curr="CONF" | curr = "BAPT") then
  557.   do
  558.     if left(curr,3) = "BAP" then curr = "BAP"
  559.     else if left(curr,3) = "CHR" then curr = "CHR"
  560.     /* note that BAPT is not official GEDCOM standard, but is for
  561.      * compatibility with long-form tags BAPTISM and BAPTISM-LDS, which are
  562.      * treated the same anyway.
  563.      */
  564.     ins = ParsePersDatePlace(curr, prn, lvl)
  565.     replay = 1
  566.   end
  567.   else if curr = "OCCU" then StoreOccup(strip(restcurr), prn)
  568.   else if curr = "EDUC" then StoreEduc(strip(restcurr), prn)
  569.   else if curr = "RELI" then StoreRelig(strip(restcurr), prn)
  570.   else if curr = "STIL" then StoreCOD("stillborn", prn)
  571.     /* Note: 'STIL' is not yet part of the official GEDCOM standard */
  572.   else if curr = "NOTE" then do
  573.     if lvl > inilvl + 1 then do
  574.       ntstr = strip(delstr(ins, 1, length(lvlstr)))
  575.       ntcurr = left(upper(word(ntstr, 1)),4)
  576.       notestr = delstr(ntstr, 1, length(ntcurr)+1)
  577.     end
  578.     else do
  579.       ntstr = lstr
  580.       ntcurr = curr
  581.       notestr = delstr(restcurr, 1, 1)
  582.     end
  583.     /* In both cases above, we only strip the first leading blank (which
  584.      * is the delimiter), and leave other leading blanks untouched.
  585.      */
  586.     if noteset = 0 then do
  587.       StorePersComment(notestr, prn)
  588.       oldnotestr = notestr
  589.       noteset = 1
  590.     end
  591.     else if donotes & (ntcurr = "NOTE" | ntcurr = "CONT" | ntcurr = "CONC") then do
  592.       nfname = notesdir||"PN"||prn||"."||dbname
  593.       if noteset = 1 then do
  594.         if ~ntovw then do
  595.       DoAppend(nfname, oldnotestr)
  596.       DoAppend(nfname, notestr)
  597.         end
  598.     else if open(notefile, nfname, 'w') then do
  599.       writeln(notefile, oldnotestr)
  600.       writeln(notefile, notestr); /* append new string */
  601.       close(notefile)
  602.     end
  603.         StorePersComment("[see notes]", prn)
  604.         noteset = 2
  605.       end
  606.       else
  607.     DoAppend(nfname, notestr); /* noteset = 2 => always append */
  608.       if prgvers >= 5 then do
  609.          PUTPERSNOTE prn nfname
  610.       end
  611.     end
  612.     else
  613.       writeln(errfile, "SKIPPED: Level "||lvlstr||" field "||ntcurr||" for person "||pnum||"! (line: "||lnum||")")
  614.   end
  615.   else if curr = "SOUR" then do
  616.     prline = strip(restcurr)
  617.     if ~refset then do
  618.       if prline ~= "" then do
  619.         prline = strip(prline,'b','@')
  620.         StorePersRefs(prline, prn)
  621.         refset = 1
  622.       end
  623.     end
  624.     else do
  625.       lostr = upper(word(strip(delstr(ins, 1, length(lvlstr))), 1))
  626.       ins = SkipField(lvl, lostr, pnum, 0, 0)
  627.       if ins ~= 0 then replay = 1
  628.     end
  629.   end
  630.   else if (curr="FAMC" | curr="FAMS" | curr="NUMB") then do
  631.     /* nothing - children and spouse relationships are established later
  632.      * and NUMB fields are considered to be irrelevant (are not even
  633.      * part of the official GEDCOM specification, btw)
  634.      * Note: we do not output a "Skipped" message for these fields.
  635.      */
  636.   end
  637.   else if (curr = "CHAN" | curr = "REFN") then do
  638.     ins = SkipChanged(lvl)
  639.     replay = 1
  640.     /* no 'SKIPPED' message for these fields */
  641.   end
  642.   else do
  643.     ins = SkipField(lvl, curr, prn, 0, 0)
  644.     if ins ~= 0 then replay = 1
  645.   end
  646. end
  647. EndString("ERROR: Unexpected end of file")
  648.  
  649. ParseFamily: PROCEDURE EXPOSE infile errfile outp usereq scrdev lnum fline flcnt FFile. FGRNArr. IRNArr. donotes dbname ntovw notesdir pgopen pscr prgvers
  650. parse arg fnum, inilvl
  651. replay = 0; fxs = 0; fins = 0
  652. finp = 0; flcnt = 0; fline = 0; FFile. = ""
  653. noteset = 0; refset = 0; oldnotestr = ""
  654.  
  655. /* replay: parse the currently read line, don't read the next one
  656.  * fxs   : family exists; if 0, only allow HUSB and WIFE, rest to temp-array
  657.  *       ~= 0, then contains FGRN (family number)
  658.  * finp  : file input; 0 = from sourcefile (GEDCOM), 1 = from temp-array
  659.  * fline : currently parsed line in temp-array / flcnt : max number of lines
  660.  * FFile : the temporary array used for this
  661.  */
  662.  
  663. do while (finp = 0 & ~eof(infile)) | (finp = 1 & (fline <= flcnt))
  664.   if replay = 0 then ins = GetNextFLine(finp)
  665.   else
  666.     replay = 0
  667.  
  668.   if ins = "" & finp = 0 then
  669.     EndString("ERROR: Unexpected end of file!")
  670.  
  671.   if finp = 1 & (fline > flcnt) then RETURN fins
  672.  
  673.   lvlstr = word(ins, 1)
  674.   lvl = GetNumType(lvlstr)
  675.   if (lvl <= inilvl) & (finp = 0) then do
  676.     if flcnt = 0 then RETURN ins
  677.     finp = 1; fline = 0
  678.     fins = ins; /* backup the currently read line */
  679.     ITERATE
  680.   end
  681.   if lvl = inilvl + 1 then do
  682.     lstr = strip(delstr(ins, 1, length(lvlstr)))
  683.     curr = upper(word(lstr, 1))
  684.     restcurr = delstr(lstr, 1, length(curr))
  685.     prsid = compress(restcurr, ' @')
  686.     if curr = "DIVORCE" then curr = "DIV"
  687.     else if curr="ANNULMENT" then curr = "ANUL"
  688.     else if curr="REFERENCE" then curr = "REFN"
  689.     else if length(curr) > 4 then curr = left(curr, 4)
  690.   end
  691.  
  692.   if curr="HUSB" then fxs = StoreFamHusband(prsid, fnum)
  693.   else if curr = "WIFE" then fxs = StoreFamWife(prsid, fnum)
  694.   else if curr="CHIL" then do
  695.     if lvl > inilvl + 1 then do
  696.       lostr = left(upper(word(strip(delstr(ins, 1, length(lvlstr))), 1)), 4)
  697.       if lostr = "ADOP" then
  698.         StoreChildAdopt(prsid)
  699.       else do
  700.         ins = SkipField(lvl, lostr, fnum, finp, 1)
  701.     if ins ~= 0 then replay = 1
  702.       end
  703.       ITERATE
  704.     end
  705.     if fxs = 0 then do
  706.       if finp = 1 then
  707.         writeln(errfile, "ERROR: Family does not exist for "||lstr||" !")
  708.       else
  709.         FOutput(ins)
  710.     end
  711.     else StoreFamChild(prsid, fxs)
  712.   end
  713.   else if (curr="MARR" | curr="DIV" | curr="ANUL" | curr="ENGA") then do
  714.     if fxs = 0 then do
  715.       if finp = 1 then
  716.         writeln(errfile, "ERROR: Family does not exist for "||lstr||" !")
  717.       else
  718.         FOutput(ins)
  719.     end
  720.     ins = ParseFamDatePlace(curr, fxs, lvl, upper(prsid), finp)
  721.     if ins ~= 0 then replay = 1
  722.   end
  723.   else if curr = "NOTE" then do
  724.     if lvl > inilvl + 1 then do
  725.       ntstr = strip(delstr(ins, 1, length(lvlstr)))
  726.       ntcurr = left(upper(word(ntstr, 1)),4)
  727.       notestr = strip(delstr(ntstr, 1, length(ntcurr)))
  728.     end
  729.     else do
  730.       ntstr = lstr
  731.       ntcurr = curr
  732.       notestr = strip(restcurr)
  733.     end
  734.     if noteset = 0 then do
  735.       if fxs ~= 0 then do
  736.         StoreFamComment(notestr, fxs)
  737.         oldnotestr = notestr
  738.         noteset = 1
  739.       end
  740.       else do
  741.         if finp = 1 then
  742.           writeln(errfile, "ERROR: Family does not exist for "||ntstr||" !")
  743.         else
  744.           FOutput(ins)
  745.       end
  746.     end
  747.     else if donotes & (ntcurr = "NOTE" | ntcurr = "CONT" | ntcurr = "CONC") then do
  748.       /* only called if noteset = 1, thus fxs ~= 0 */
  749.       if fxs ~= 0 then do
  750.         nfname = notesdir||"FN"||fxs||"."||dbname
  751.         if noteset = 1 then do
  752.           if ~ntovw then do
  753.         DoAppend(nfname, oldnotestr)
  754.         DoAppend(nfname, notestr)
  755.           end
  756.       else if open(notefile, nfname, 'w') then do
  757.         writeln(notefile, oldnotestr)
  758.         writeln(notefile, notestr); /* append new string */
  759.         close(notefile)
  760.       end
  761.           StoreFamComment("[see notes]", fxs)
  762.       noteset = 2
  763.         end
  764.         else
  765.       DoAppend(nfname, notestr); /* noteset = 2 => always append */
  766.         if prgvers >= 5 then do
  767.           PUTFAMNOTE fxs nfname
  768.         end
  769.       end
  770.       else
  771.         writeln(errfile, "ERROR: Family for "||ntstr||" doesn't exist!")
  772.     end
  773.     else
  774.       writeln(errfile, "SKIPPED: Level "||lvlstr||" field "||ntcurr||" for family "||fnum||"! (line: "||lnum||")")
  775.   end
  776.   else if curr = "SOUR" then do
  777.     frline = strip(restcurr)
  778.     if ~refset then do
  779.       if frline ~= "" then do
  780.     frline = strip(frline,'b','@')
  781.     if fxs ~= 0 then do
  782.       PUTFAMREFS fxs frline
  783.       refset = 1
  784.         end
  785.     else do
  786.       if finp = 1 then
  787.         writeln(errfile, "ERROR: Family does not exist for "||lstr||" !")
  788.       else
  789.         FOutput(ins)
  790.     end
  791.       end
  792.     end
  793.     else do
  794.       lostr = upper(word(strip(delstr(ins, 1, length(lvlstr))), 1))
  795.       ins = SkipField(lvl, lostr, fnum, finp, 1)
  796.       if ins ~= 0 then replay = 1
  797.     end
  798.   end
  799.   else if curr = "NUMB" | curr = "REFN" then do
  800.     /* No SKIPPED message for these fields, as they are irrelevant.
  801.      * NUMB fields are not even part of the official GEDCOM specification.
  802.      * It's just here because eg. ROOTS uses them a lot, and I don't want
  803.      * the program to output a "SKIPPED" line for them in the log file.
  804.      */
  805.   end
  806.   else if curr = "CHAN" then do
  807.     ins = SkipChanged(lvl)
  808.     replay = 1
  809.     /* no 'SKIPPED' message for these fields */
  810.   end
  811.   else do
  812.     ins = SkipField(lvl, curr, fnum, finp, 1)
  813.     if ins ~= 0 then replay = 1
  814.   end
  815. end
  816. if finp = 1 then RETURN fins
  817. EndString("ERROR: Unexpected end of file!")
  818.  
  819. GetNumType: PROCEDURE EXPOSE outp infile usereq lnum pgopen pscr scrdev
  820. parse arg str
  821. if ~DATATYPE(str, 'w') then
  822.   EndString("ERROR: Level indicator expected -> error in GEDCOM specification?"||'0A'x||"String is "||str||" (line: "||lnum||")")
  823. return str + 1
  824.  
  825. GetNextFLine: PROCEDURE EXPOSE infile fline flcnt lnum FFile.
  826. parse arg finp
  827. if finp = 0 then return GetNextLine()
  828. ignl = ""
  829. do while ignl = "" & fline <= flcnt
  830.   fline = fline + 1
  831.   ignl = FFile.fline
  832.   if ignl ~= "" then ignl = strip(ignl, 'B', '     ')
  833.   /* strip leading and trailing spaces, tabs and ^M (linefeed) characters */
  834.   /* also skip empty lines */
  835. end
  836. return ignl
  837.  
  838. GetNextLine: PROCEDURE EXPOSE infile lnum
  839. lnum = lnum + 1
  840. ignl = ""
  841. do while ignl = "" & ~eof(infile)
  842.   ignl = readln(infile)
  843.   if ignl ~= "" then ignl = strip(ignl, 'B', '     ')
  844.   /* strip leading and trailing spaces, tabs and ^M (linefeed) characters */
  845.   /* also skip empty lines */
  846. end
  847. return ignl
  848.  
  849. FOutput: PROCEDURE EXPOSE flcnt FFile.
  850. parse arg iline
  851. FFile.flcnt = iline
  852. flcnt = flcnt + 1
  853. return 0
  854.  
  855. StorePersName: PROCEDURE
  856. parse arg nstr, pnum
  857. nstr = strip(nstr, 'B', '/')
  858. ps = pos('/', nstr)
  859. if ps = 0 then do
  860.   fname = ""
  861.   lname = nstr
  862. end
  863. else do
  864.   fname = left(nstr, ps-1)
  865.   lname = compress(right(nstr, length(nstr)-ps),'/')
  866. end
  867. PUTLASTNAME pnum lname
  868. PUTFIRSTNAME pnum fname
  869. return 1
  870.  
  871. StorePersSex: PROCEDURE
  872. parse arg nstr, pnum
  873.  sxstr = upper(left(nstr, 1))
  874.  if sxstr ~= 'M' then sxstr = 'F'
  875. PUTSEX pnum sxstr
  876. return 1
  877.  
  878. ParsePersDatePlace: PROCEDURE EXPOSE infile errfile outp usereq scrdev lnum pgopen pscr
  879. parse arg idstr, pnum, inilvl
  880. datstr = ""
  881. plcstr = ""
  882. causestr = ""
  883. do while ~eof(infile)
  884.   ins = GetNextLine()
  885.   if eof(infile) then
  886.     EndString("ERROR: Unexpected end of file!")
  887.   lvlstr = word(ins, 1)
  888.   lvl = GetNumType(lvlstr)
  889.   if lvl <= inilvl then do
  890.     select
  891.       when idstr = "BIRT" then do
  892.     if datstr ~= "" then do
  893.       PUTBIRTHDATE pnum datstr
  894.           if RESULT = 0 then
  895.             writeln(errfile, "Error in birth date '"||datstr||" for person "||pnum)
  896.         end
  897.     if plcstr ~= "" then
  898.       PUTBIRTHPLACE pnum plcstr
  899.       end
  900.       when idstr = "DEAT" then do
  901.     if datstr ~= "" then do
  902.       PUTDEATHDATE pnum datstr
  903.           if RESULT = 0 then
  904.             writeln(errfile, "Error in death date '"||datstr||" for person "||pnum)
  905.         end
  906.     if plcstr ~= "" then
  907.       PUTDEATHPLACE pnum plcstr
  908.     if causestr ~= "" then
  909.       PUTDIEDOF pnum causestr
  910.       end
  911.       when idstr = "BURI" then do
  912.     if datstr ~= "" then do
  913.       PUTBURIALDATE pnum datstr
  914.           if RESULT = 0 then
  915.             writeln(errfile, "Error in burial date '"||datstr||" for person "||pnum)
  916.         end
  917.     if plcstr ~= "" then
  918.       PUTBURIALPLACE pnum plcstr
  919.       end
  920.       when (idstr="BAP" | idstr="CHR" | idstr="CONF") then do
  921.     if datstr ~= "" then do
  922.       PUTBAPTISMDATE pnum datstr
  923.           if RESULT = 0 then
  924.             writeln(errfile, "Error in baptism date '"||datstr||" for person "||pnum)
  925.         end
  926.     if plcstr ~= "" then
  927.       PUTBAPTISMPLACE pnum plcstr
  928.       end
  929.       otherwise
  930.         /* do nothing */
  931.     end
  932.     RETURN ins
  933.   end
  934.   else if lvl = inilvl+1 then do
  935.     lstr = strip(delstr(ins, 1, length(lvlstr)))
  936.     curr = upper(word(lstr, 1))
  937.     if curr = "DATE" then do
  938.       datstr = ParseDate(strip(delstr(lstr, 1, length(curr))))
  939.       /* TO DO: add some more parsing of the date string
  940.        * - dates ending on /x (indicating a choice of years)
  941.        * - "BET" dates (between 2 dates)
  942.        * etc.
  943.        */
  944.     end
  945.     else if (curr="PLAC" | curr="PLACE") then do
  946.       plcstr = strip(delstr(lstr, 1, length(curr)))
  947.     end
  948.     else if (curr="QUAY" | curr="QUALITY_OF_DATA") then do
  949.       /* only add '?' for QUAY 0 fields */
  950.       lstr = strip(delstr(lstr, 1, length(curr)))
  951.       if DATATYPE(lstr, 'w') & lstr < 1 then do
  952.         if datstr ~= "" then datstr = datstr||'?'
  953.         if plcstr ~= "" then plcstr = plcstr||'?'
  954.       end
  955.     end
  956.     else if (curr="CAUS" | curr="CAUSE") then do
  957.       causestr = strip(delstr(lstr, 1, length(curr)))
  958.     end
  959.   end
  960.   else do
  961.     /* lvl > inilvl+1 */
  962.     qlstr = strip(delstr(ins, 1, length(lvlstr)))
  963.     qcurr = upper(word(qlstr, 1))
  964.     if (qcurr="QUAY" | qcurr="QUALITY_OF_DATA") then do
  965.       /* only add '?' for QUAY 0 fields */
  966.       qlstr = strip(delstr(qlstr, 1, length(qcurr)))
  967.       if DATATYPE(qlstr, 'w') & qlstr < 1 then do
  968.         if curr = "DATE" & datstr ~= "" then
  969.       datstr = ParseDate(datstr)||'?'
  970.         if (curr = "PLAC" | curr = "PLACE") & plcstr ~= "" then
  971.       plcstr = plcstr||'?'
  972.       end
  973.     end
  974.     else do
  975.       /* else: skip all other fields of level inilvl+1 */
  976.       writeln(errfile, "SKIPPED: Level "||lvlstr||" field "||qcurr||" for person "||pnum||"! (line: "||lnum||")")
  977.     end
  978.   end
  979. end
  980. return 0
  981.  
  982. ParseFamDatePlace: PROCEDURE EXPOSE infile errfile outp usereq scrdev lnum fline flcnt pgopen pscr FFile. FGRNArr.
  983. parse arg idstr, ff, inilvl, idr, finp
  984. datstr = ""; plcstr = ""; clbrnt = ""; wtness = ""
  985. if idstr="ANUL" then divtype = 4
  986. else if (idr = "N" & idstr="DIV") then divtype = 1
  987. else divtype = 2
  988. /* some programs (like PAF 2.2) have "DIV Y" and "DIV N" fields
  989.  * DIV Y (yes) is treated identical to "DIV" (without arguments)
  990.  * DIV N (no)  is treated as 'Ending: None'
  991.  */
  992. do while ~eof(infile) | (fline < flcnt)
  993.   ins = GetNextFLine(finp)
  994.  
  995.   if finp = 0 & ins = "" then
  996.     EndString("ERROR: Unexpected end of file (Parsing Family Events)!")
  997.  
  998.   if finp = 1 & (fline > flcnt) then do
  999.     if ff ~= 0 then do
  1000.       if idstr="MARR" then do
  1001.         if datstr ~= "" then do
  1002.       PUTMARRYDATE ff datstr
  1003.           if RESULT = 0 then
  1004.             writeln(errfile, "Error in marriage date '"||datstr||" for family "||ff)
  1005.         end
  1006.         if plcstr ~= "" then
  1007.       PUTMARRYPLACE ff plcstr
  1008.         if clbrnt ~= "" then
  1009.           PUTCELEBRANT ff clbrnt
  1010.         if wtness ~= "" then
  1011.           PUTWITNESS ff wtness
  1012.       end
  1013.       else if (idstr="ANUL" | idstr="DIV") then do
  1014.         if datstr ~= "" then do
  1015.       PUTENDDATE ff datstr
  1016.           if RESULT = 0 then
  1017.             writeln(errfile, "Error in ending date '"||datstr||" for family "||ff)
  1018.         end
  1019.         if plcstr ~= "" then
  1020.       PUTENDPLACE ff plcstr
  1021.     PUTENDING ff divtype
  1022.       end
  1023.       else if idstr="ENGA" then do
  1024.         if datstr ~= "" then do
  1025.       PUTENGAGEDATE ff datstr
  1026.           if RESULT = 0 then
  1027.             writeln(errfile, "Error in engagement date '"||datstr||" for family "||ff)
  1028.         end
  1029.         if plcstr ~= "" then
  1030.       PUTENGAGEPLACE ff plcstr
  1031.       end
  1032.     end
  1033.     RETURN 0
  1034.   end
  1035.  
  1036.   lvlstr = word(ins, 1)
  1037.   lvl = GetNumType(lvlstr)
  1038.   if lvl <= inilvl then do
  1039.     if ff ~= 0 then do
  1040.       if idstr="MARR" then do
  1041.         if datstr ~= "" then do
  1042.       PUTMARRYDATE ff datstr
  1043.           if RESULT = 0 then
  1044.             writeln(errfile, "Error in marriage date '"||datstr||" for family "||ff)
  1045.         end
  1046.         if plcstr ~= "" then
  1047.       PUTMARRYPLACE ff plcstr
  1048.         if clbrnt ~= "" then
  1049.           PUTCELEBRANT ff clbrnt
  1050.         if wtness ~= "" then
  1051.           PUTWITNESS ff wtness
  1052.       end
  1053.       else if (idstr="DIV" | idstr="ANUL") then do
  1054.         if datstr ~= "" then do
  1055.       PUTENDDATE ff datstr
  1056.           if RESULT = 0 then
  1057.             writeln(errfile, "Error in ending date '"||datstr||" for family "||ff)
  1058.         end
  1059.         if plcstr ~= "" then
  1060.       PUTENDPLACE ff plcstr
  1061.         PUTENDING ff divtype
  1062.       end
  1063.       else if idstr="ENGA" then do
  1064.         if datstr ~= "" then do
  1065.       PUTENGAGEDATE ff datstr
  1066.           if RESULT = 0 then
  1067.             writeln(errfile, "Error in engagement date '"||datstr||" for family "||ff)
  1068.         end
  1069.         if plcstr ~= "" then
  1070.       PUTENGAGEPLACE ff plcstr
  1071.       end
  1072.     end
  1073.     RETURN ins
  1074.   end
  1075.   if finp = 0 & ff = 0 then FOutput(ins)
  1076.   else do
  1077.     if lvl = inilvl+1 then do
  1078.       lstr = strip(delstr(ins, 1, length(lvlstr)))
  1079.       curr = upper(word(lstr, 1))
  1080.       if curr="QUALITY_OF_DATA" then curr = "QUAY"
  1081.       else if length(curr) > 4 then curr = left(curr, 4)
  1082.       if curr = "DATE" then do
  1083.         datstr = ParseDate(strip(delstr(lstr, 1, length(curr))))
  1084.         /* TO DO: add some more parsing of the date string */
  1085.       end
  1086.       else if curr="PLAC" then do
  1087.         plcstr = strip(delstr(lstr, 1, length(curr)))
  1088.       end
  1089.       else if curr="OFFI" then do
  1090.         clbrnt = strip(delstr(lstr, 1, length(curr)))
  1091.         /* only for "MARR" */
  1092.       end
  1093.       else if curr="WITN" then do
  1094.         wtness = strip(delstr(lstr, 1, length(curr)))
  1095.         /* only for "MARR" */
  1096.       end
  1097.       else if curr="QUAY" then do
  1098.         /* only add '?' for QUAY 0 fields */
  1099.         lstr = strip(delstr(lstr, 1, length(curr)))
  1100.         if DATATYPE(lstr, 'w') & lstr < 1 then do
  1101.           if datstr ~= "" then datstr = ParseDate(datstr)||'?'
  1102.           if plcstr ~= "" then plcstr = plcstr||'?'
  1103.         end
  1104.       end
  1105.       else if (curr = "TYPE" & idstr = "DIV") then do
  1106.         lstr = upper(strip(delstr(lstr, 1, length(curr))))
  1107.         if left(lstr, 3) = "SEP" then divtype = 3
  1108.         else if left(lstr, 4) = "DEAT" then divtype = 5
  1109.         else divtype = 2
  1110.         /* default is 'DIVORCE' */
  1111.       end
  1112.     end
  1113.     else if lvl > inilvl + 1 then do
  1114.       qlstr = strip(delstr(ins, 1, length(lvlstr)))
  1115.       qcurr = upper(word(qlstr, 1))
  1116.       if (qcurr="QUAY" | qcurr="QUALITY_OF_DATA") then do
  1117.         /* only add '?' for QUAY 0 fields */
  1118.         qlstr = strip(delstr(qlstr, 1, length(qcurr)))
  1119.         if DATATYPE(qlstr, 'w') & qlstr < 1 then do
  1120.           if curr = "DATE" & datstr ~= "" then
  1121.         datstr = ParseDate(datstr)||'?'
  1122.           if curr = "PLAC" & plcstr ~= "" then
  1123.         plcstr = plcstr||'?'
  1124.         end
  1125.       end
  1126.       else do
  1127.         /* else: skip all other fields of level inilvl+1 */
  1128.         writeln(errfile, "SKIPPED: Level "||lvlstr||" field "||qcurr||" for family "||ff||"! (line: "||lnum||")")
  1129.       end
  1130.     end
  1131.   end
  1132. end
  1133. EndString("ERROR: Unexpected end of file (Parsed Family Events)!")
  1134.  
  1135. GetNewPerson: PROCEDURE EXPOSE infile outp usereq scrdev pgopen pscr
  1136.   PUTNEWPERSON
  1137.   newpnum = RESULT
  1138.   if newpnum = 0 then EndString("ERROR: Cannot allocate new person!")
  1139.   /* if you want to see Scion in action, uncomment the next line */
  1140.   /* GETPERSONWIN newpnum */
  1141. return newpnum
  1142.  
  1143. GetNewFamily: PROCEDURE EXPOSE infile outp usereq scrdev pgopen pscr
  1144. parse arg irn
  1145.   PUTNEWFAMILY irn
  1146.   newfnum = RESULT
  1147.   if newfnum = 0 then EndString("ERROR: Cannot allocate new family!")
  1148.   /* if you want to see Scion in action, uncomment the next line */
  1149.   /* GETFAMILYWIN newfnum */
  1150. return newfnum
  1151.  
  1152. StoreOccup: PROCEDURE
  1153. parse arg nstr, pnum
  1154.  PUTOCCUPATION pnum nstr
  1155. return 1
  1156.  
  1157. StoreEduc: PROCEDURE
  1158. parse arg nstr, pnum
  1159.  PUTEDUCATION pnum nstr
  1160. return 1
  1161.  
  1162. StoreRelig: PROCEDURE
  1163. parse arg nstr, pnum
  1164.  PUTRELIGION pnum nstr
  1165. return 1
  1166.  
  1167. StoreCOD: PROCEDURE
  1168. parse arg nstr, pnum
  1169.  PUTDIEDOF pnum nstr
  1170. return 1
  1171.  
  1172. StorePersComment: PROCEDURE
  1173. parse arg nstr, pnum
  1174.   if pnum ~= 0 then
  1175.     PUTPERSCOMMENT pnum nstr
  1176. return 1
  1177.  
  1178. StorePersRefs: PROCEDURE
  1179. parse arg nstr, pnum
  1180.   if pnum ~= 0 then
  1181.     PUTPERSREFS pnum nstr
  1182. return 1
  1183.  
  1184. StoreFamHusband: PROCEDURE EXPOSE errfile infile outp usereq scrdev lnum IRNArr. FGRNArr.
  1185. parse arg nstr, fnum
  1186.   nstr = compress(nstr,'@ ')
  1187.   ff = 0
  1188.   ii = GGetIRN(nstr)
  1189.   if ii = 0 then
  1190.     writeln(errfile, "ERROR: Missing Personal Record for HUSBAND "||nstr||" (line: "||lnum||")")
  1191.   else do
  1192.      ff = GGetFGRN(fnum)
  1193.      if ff = 0 then do
  1194.     ff = GetNewFamily(ii)
  1195.     FGRNArr.0 = FGRNArr.0||fnum||' '
  1196.     FGRNArr.1 = FGRNArr.1||ff||' '
  1197.      end
  1198.      else do
  1199.         /* There already is a family, so there is a principal; assume
  1200.      * that that is the wife - add the husband as spouse
  1201.          */
  1202.         PUTSPOUSE ff ii
  1203.         ers = RESULT
  1204.         if ers ~= 1 then do
  1205.            writeln(errfile, "ERROR "||ers||" in PUTSPOUSE (HUSB) "||ff||' '||ii)
  1206.        GETPRINCIPAL ff
  1207.        prc = RESULT
  1208.        GETSPOUSE ff
  1209.        spc = RESULT
  1210.        writeln(errfile, "Principal: "||prc||", Spouse: "||spc)
  1211.         end
  1212.      end
  1213.   end
  1214. return ff
  1215.  
  1216. StoreFamWife: PROCEDURE EXPOSE errfile infile outp usereq scrdev lnum IRNArr. FGRNArr.
  1217. parse arg nstr, fnum
  1218.   nstr = compress(nstr,'@ ')
  1219.   ff = 0
  1220.   ii = GGetIRN(nstr)
  1221.   if ii = 0 then
  1222.     writeln(errfile, "ERROR: Missing Personal Record for WIFE "||nstr||" (line: "||lnum||")")
  1223.   else do
  1224.      ff = GGetFGRN(fnum)
  1225.      if ff = 0 then do
  1226.         ff = GetNewFamily(ii)
  1227.         FGRNArr.0 = FGRNArr.0||fnum||' '
  1228.         FGRNArr.1 = FGRNArr.1||ff||' '
  1229.      end
  1230.      else do
  1231.         PUTSPOUSE ff ii
  1232.         ers = RESULT
  1233.         if ers ~= 1 then do
  1234.            writeln(errfile, "ERROR "||ers||" in PUTSPOUSE (WIFE) "||ff||' '||ii)
  1235.        GETPRINCIPAL ff
  1236.        prc = RESULT
  1237.            GETSPOUSE ff
  1238.            spc = RESULT
  1239.        writeln(errfile, "Principal: "||prc||", Spouse: "||spc)
  1240.         end
  1241.      end
  1242.   end
  1243. return ff
  1244.  
  1245. StoreFamChild: PROCEDURE EXPOSE errfile infile outp usereq scrdev lnum IRNArr. FGRNArr.
  1246. parse arg nstr, fnum
  1247. /* TO DO: improve this function, to allow definition of children here,
  1248.  *      instead of in a separate personal record. Also look for "ADOP"
  1249.  *      field (adopted children)
  1250.  */
  1251.   if fnum = 0 then RETURN 0
  1252.     /* we cannot parse a child when there is no family yet */
  1253.   nstr = compress(nstr,'@ ')
  1254.   ii = GGetIRN(nstr)
  1255.   if ii = 0 then
  1256.     writeln(errfile, "ERROR: Missing Personal Record for CHILD "||nstr||" (line: "||lnum||")")
  1257.   else do
  1258.     PUTCHILD fnum ii
  1259.     ers = RESULT
  1260.     if ers ~= 1 then
  1261.       writeln(errfile, "ERROR "||ers||" in PUTCHILD "||fnum||' '||ii||" (line: "||lnum||")")
  1262.   end
  1263. return 1
  1264.  
  1265. StoreChildAdopt: PROCEDURE EXPOSE errfile infile outp usereq scrdev lnum IRNArr.
  1266. parse arg nstr
  1267.   /* This uses an as yet undocumented (and maybe even unsupported) feature
  1268.    * in Scion v4.07 and above.  So don't be surprised if you see italicized
  1269.    * names in the Family Details window (try alt-clicking on a child to
  1270.    * toggle this)
  1271.    */
  1272.   nstr = compress(nstr,'@ ')
  1273.   ii = GGetIRN(nstr)
  1274.   if ii = 0 then
  1275.     writeln(errfile, "ERROR: Missing Personal Record for CHILD "||nstr||" (line: "||lnum||")")
  1276.   else
  1277.     PUTADOPTION ii 1
  1278. return 1
  1279.  
  1280. StoreFamComment: PROCEDURE
  1281. parse arg nstr, ff
  1282.   PUTFAMCOMMENT ff nstr
  1283. return 1
  1284.  
  1285. /* Return the Scion IRN belonging to the GEDCOM Personal number pnum */
  1286. GGetIRN: PROCEDURE EXPOSE IRNArr.
  1287. parse arg pnum
  1288. anum = find(IRNArr.0, pnum)
  1289. if anum > 0 then
  1290.   return word(IRNArr.1, anum)
  1291. else
  1292.   return 0
  1293.  
  1294. /* Return the Scion FGRN belonging to the GEDCOM Family number fnum */
  1295. GGetFGRN: PROCEDURE EXPOSE lnum FGRNArr.
  1296. parse arg fnum
  1297. anum = find(FGRNArr.0, fnum)
  1298. if anum > 0 then
  1299.   return word(FGRNArr.1, anum)
  1300. else
  1301.   return 0
  1302.  
  1303. DoAppend: PROCEDURE 
  1304. parse arg fname, ostr
  1305. if exists(fname) then
  1306.   rval = open(notefile, fname, 'a')
  1307. else
  1308.   rval = open(notefile, fname, 'w')
  1309. if rval then do
  1310.   writeln(notefile, ostr)
  1311.   close(notefile)
  1312. end
  1313. return 0
  1314.  
  1315. SkipChanged: PROCEDURE EXPOSE infile lnum
  1316. parse arg inlvl
  1317. lvl = inlvl + 1
  1318. do until lvl <= inlvl
  1319.   ins = GetNextLine()
  1320.   lvlstr = word(ins, 1)
  1321.   lvl = GetNumType(lvlstr)
  1322. end
  1323. return ins
  1324.  
  1325. SkipField: PROCEDURE EXPOSE infile errfile outp usereq scrdev lnum fline flcnt pgopen pscr FFile.
  1326. arg inilvl, lostr, xnum, finp, pflag
  1327.   if pflag = 0 then
  1328.     writeln(errfile, "SKIPPED: Level "||inilvl||" field "||lostr||" for person "||xnum||"! (line: "||lnum||")")
  1329.   else
  1330.     writeln(errfile, "SKIPPED: Level "||inilvl||" field "||lostr||" for family "||xnum||"! (line: "||lnum||")")
  1331.   do while ~eof(infile) | (pflag = 1 & fline < flcnt)
  1332.     ins = GetNextFLine(finp)
  1333.     if finp = 0 & ins = "" then
  1334.       EndString("ERROR: Unexpected end of file (while skipping fields)!")
  1335.     lvlstr = word(ins, 1)
  1336.     lvl = GetNumType(lvlstr)
  1337.     if lvl <= inilvl then RETURN ins
  1338.     writeln(errfile, "SKIPPING: Level "||lvl||"("||inilvl||") line: '"||ins||"'")
  1339.   end
  1340. EndString("ERROR: Unexpected end of file (while skipping fields)!")
  1341.  
  1342. /*
  1343.  * Procedure to strip the directory path from the string,
  1344.  * only leaving the filename
  1345.  */
  1346. StripPath: PROCEDURE
  1347. parse arg str
  1348.   p = lastpos('/', str)
  1349.   if p > 0 then ret1 = delstr(str,1,p)
  1350.   else ret1 = str
  1351.   p = lastpos(':', ret1)
  1352.   if p > 0 then retstr = delstr(ret1,1,p)
  1353.   else retstr = ret1
  1354. return retstr
  1355.  
  1356. /*
  1357.  * Procedure to strip the date string so that it has the correct
  1358.  * format (for Scion v5 and higher)
  1359.  */
  1360. ParseDate: PROCEDURE EXPOSE prgvers
  1361. parse arg datestr
  1362. if prgvers < 5 | left(datestr, 3) = '%%%' then return datestr
  1363. retstr = ""; prefstr = ""; leftstr = ""
  1364. datestr = upper(strip(datestr, 'b'))
  1365. if right(datestr, 4) = 'B.C.' then do
  1366.    retstr = retstr||'BC'
  1367.    datestr = strip(left(datestr, length(datestr)-4), 't')
  1368. end
  1369. else if right(datestr, 2) = 'BC' then do
  1370.    retstr = retstr||' BC'
  1371.    datestr = strip(left(datestr, length(datestr)-2), 't')
  1372. end
  1373. yearstr=""
  1374. wordcnt = words(datestr)
  1375. if wordcnt > 0 then do
  1376.    /* get year */
  1377.    yearstr = word(datestr, wordcnt)
  1378.    datestr = left(datestr, length(datestr)-length(yearstr))
  1379.    startix = verify(yearstr, "0123456789", 'm')
  1380.    if startix > 1 then do
  1381.      datestr = datestr||left(yearstr, startix - 1)
  1382.      yearstr = right(yearstr, length(yearstr) - startix + 1)
  1383.    end
  1384.    else if startix = 0 then do
  1385.      yearstr = "0000"
  1386.    end
  1387.    endix = verify(yearstr, "0123456789")
  1388.    if endix > 0 then do
  1389.       yearstr = left(yearstr, endix - 1)
  1390.    end
  1391.    if length(yearstr) > 4 then do
  1392.      yearstr = left(yearstr, 4)
  1393.    end
  1394.    datestr = strip(datestr, 't')
  1395. end
  1396. retstr = right("0000"||yearstr, 4)||retstr
  1397. wordcnt = words(datestr)
  1398. if wordcnt > 0 then do
  1399.   w1str = word(datestr, 1)
  1400.   modstr = left(w1str, 3)
  1401.   modix = find("ABT ABO EST AFT BEF PRE", modstr)
  1402.   if modix > 0 then do
  1403.     datestr = strip(right(datestr, length(datestr)-length(w1str)), 'L', ' -')
  1404.     prefstr = word("~ ~ ~ > < <", modix)
  1405.   end
  1406.   else if modstr = "BET" then do
  1407.     datestr = strip(right(datestr, length(datestr)-length(w1str)), 'L')
  1408.     prefstr = '<'
  1409.     rightix = index(datestr, " AND")
  1410.     if rightix > 0 then do
  1411.       leftstr = ParseDate(left(datestr, rightix))
  1412.       /* what to do with this first date? */
  1413.       datestr = strip(right(datestr, length(datestr) - rightix + 1), 'b')
  1414.     end
  1415.   end
  1416. end
  1417. /* scan for month */
  1418. monthix = 0
  1419. wordcnt = words(datestr)
  1420. if wordcnt > 0 then do
  1421.   monthstr = word(datestr, wordcnt)
  1422.   monthix = find("JAN FEB MAR APR MAY JUN JUL AUG SEP OCT NOV DEC", monthstr)
  1423.   datestr = strip(left(datestr, length(datestr)-length(monthstr)), 'T')
  1424. end
  1425. retstr = right("00"||monthix, 2)||"-"||retstr
  1426. /* scan for day */
  1427. wordcnt = wordcnt - 1
  1428. daystr = ""
  1429. if wordcnt > 0 then do
  1430.    daystr = word(datestr, wordcnt)
  1431.    datestr = strip(left(datestr, length(datestr)-length(daystr)), 't')
  1432.    startix = verify(daystr, "0123456789", 'm')
  1433.    if startix > 1 then do
  1434.      daystr = right(daystr, length(daystr) - startix + 1)
  1435.    end
  1436.    else if startix = 0 then do
  1437.      daystr = "00"
  1438.    end
  1439.    endix = verify(daystr, "0123456789")
  1440.    if endix > 0 then do
  1441.       daystr = left(daystr, endix - 1)
  1442.    end
  1443.    if length(daystr) > 2 then do
  1444.      daystr = left(daystr, 2)
  1445.    end
  1446. end
  1447. retstr = right("00"||daystr, 2)||"-"||retstr
  1448. prefstr = "%%%"||prefstr
  1449. return prefstr||retstr
  1450.  
  1451. Tell: PROCEDURE EXPOSE outp scrdev
  1452. parse arg str
  1453. if outp then writeln(scrdev, str)
  1454. return 0
  1455.  
  1456. TellNN: PROCEDURE EXPOSE outp scrdev
  1457. parse arg str
  1458. if outp then writech(scrdev, str)
  1459. return 0
  1460.  
  1461. EndString: PROCEDURE EXPOSE usereq outp pgopen pscr scrdev infile
  1462. parse arg str
  1463. if pgopen then Postmsg()
  1464. /* If you turned off stdout, no error messages will be shown! */
  1465. if usereq then
  1466.   rtezrequest(str,'E_xit','Converter Message:','rt_pubscrname = '||PSCR)
  1467. else
  1468.   Tell(str || '0A'x)
  1469. if outp & ~usereq & (scrdev ~= stdout) then do
  1470.   Tell("Press <return> to exit.")
  1471.   readln(scrdev)
  1472.   close(scrdev)
  1473. end
  1474. close(infile)
  1475. EXIT
  1476.  
  1477. /* Let's make sure you get a nice message when you turn off the printer :-) */
  1478.  
  1479. IOERR:
  1480.   bline = SIGL
  1481.   say "I/O error #"||RC||" detected in line "||bline||":"
  1482.   say sourceline(bline)
  1483.   if pgopen then Postmsg()
  1484.   EXIT
  1485.